package com.example.lottery.service.impl;

import com.example.lottery.common.errorcode.ServiceErrorCodeConstants;
import com.example.lottery.common.exception.ServiceException;
import com.example.lottery.common.utils.JacksonUtil;
import com.example.lottery.common.utils.RedisUtil;
import com.example.lottery.controller.param.CreateActivityParam;
import com.example.lottery.controller.param.CreatePrizeByActivityParam;
import com.example.lottery.controller.param.CreateUserByActivityParam;
import com.example.lottery.controller.param.PageParam;
import com.example.lottery.dao.datobject.ActivityDO;
import com.example.lottery.dao.datobject.ActivityPrizeDO;
import com.example.lottery.dao.datobject.ActivityUserDO;
import com.example.lottery.dao.datobject.PrizeDO;
import com.example.lottery.dao.mapper.*;
import com.example.lottery.service.ActivityService;
import com.example.lottery.service.dto.ActivityDTO;
import com.example.lottery.service.dto.ActivityDetailDTO;
import com.example.lottery.service.dto.CreateActivityDTO;
import com.example.lottery.service.dto.PageListDTO;
import com.example.lottery.service.enums.ActivityPrizeStatusEnum;
import com.example.lottery.service.enums.ActivityPrizeTiersEnum;
import com.example.lottery.service.enums.ActivityStatusEnum;
import com.example.lottery.service.enums.ActivityUserStatusEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
@Slf4j
public class ActivityServiceImpl implements ActivityService {
    /**
     * 活动缓存前置，为了区分业务  key的前缀
     */
    private final String ACTIVITY_PREFIX = "ACTIVITY_";
    /**
     * 活动缓存过期时间  3天
     */
    private final Long ACTIVITY_TIMEOUT = 60 * 60 * 24 * 3L;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private PrizeMapper prizeMapper;
    @Autowired
    private ActivityMapper activityMapper;
    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;
    @Autowired
    private ActivityUserMapper activityUserMapper;
    @Autowired
    private RedisUtil redisUtil;
    @Override
    @Transactional(rollbackFor = Exception.class)
    //只要活动表 活动人员表 活动奖励表 其中一个表出了问题 都要进行回滚
    public CreateActivityDTO createActivity(CreateActivityParam param) {
        //检验活动信息是否正确
        checkActivity(param);
        //保存活动信息
        ActivityDO activityDO=new ActivityDO();
        activityDO.setActivityName(param.getActivityName());
        activityDO.setDescription(param.getDescription());
        activityDO.setStatus(ActivityStatusEnum.RUNNING.name());
        activityMapper.insert(activityDO);

        //保存管理的奖品信息
        List<CreatePrizeByActivityParam> prizeParams=param.getActivityPrizeList();
        List<ActivityPrizeDO> activityPrizeDOList=prizeParams.stream()
//        prizeParam 就是 遍历 prizeParams 列表时的当前元素
                .map(prizeParam ->{
                    ActivityPrizeDO activityPrizeDO=new ActivityPrizeDO();
                    //这里马上就要使用活动的id了
                    activityPrizeDO.setActivityId(activityDO.getId());
                    activityPrizeDO.setPrizeId(prizeParam.getPrizeId());
                    activityPrizeDO.setPrizeAmount(prizeParam.getPrizeAmount());
                    activityPrizeDO.setPrizeTiers(prizeParam.getPrizeTiers());
                    activityPrizeDO.setStatus(ActivityPrizeStatusEnum.INIT.name());
                    return activityPrizeDO;
                }).collect(Collectors.toList());
        activityPrizeMapper.batchInsert(activityPrizeDOList);

        //保存活动关联的人员信息
        List<CreateUserByActivityParam> userParams=param.getActivityUserList();
        List<ActivityUserDO> activityUserDOList=userParams.stream()
                .map(userParam->{
                    ActivityUserDO activityUserDO=new ActivityUserDO();
                    activityUserDO.setActivityId(activityDO.getId());
                    activityUserDO.setUserId(userParam.getUserId());
                    activityUserDO.setUserName(userParam.getUserName());
                    activityUserDO.setStatus(ActivityUserStatusEnum.INIT.name());
                    return activityUserDO;
                }).collect(Collectors.toList());
        activityUserMapper.bathchInsert(activityUserDOList);

        //整合完整的活动信息,存放redis
        //ActivityDetailDTO :活动+奖品+人员
        //先获取奖品基本属性 列表
        //获取需要查询的奖品id
        //为什么要获得奖品的基本属性呢 明明已经有activityPrizeDOList  因为你创建活动的时候 要勾选奖品 此时要奖品的具体信息
        //但是activityPrizeDOList没有勾选活动所需要的具体信息
        List<Long> prizeIds=param.getActivityPrizeList().stream()
                .map(CreatePrizeByActivityParam::getPrizeId)
                .distinct()
                .collect(Collectors.toList());
        List<PrizeDO> prizeDOList=prizeMapper.batchSelectByIds(prizeIds);
        ActivityDetailDTO detailDTO=convertToActivityDetailDTO(activityDO,activityUserDOList,prizeDOList,
                activityPrizeDOList);
        cacheActivity(detailDTO);

        //构造返回
        CreateActivityDTO createActivityDTO=new CreateActivityDTO();
        createActivityDTO.setActivityId(activityDO.getId());
        return createActivityDTO;
    }


    /**
     * 缓存完整的活动信息
     * @param detailDTO
     */
    private void cacheActivity(ActivityDetailDTO detailDTO) {
        //key  ACTIVITY_detailDTO.gettActivityId()
        //value ActivityDetailDTO  这个对象 json字符串
        if(detailDTO==null||detailDTO.getActivityId()==null){
            log.warn("要缓存的活动信息不存在!");
            return;
        }
        try{
            redisUtil.set(ACTIVITY_PREFIX+detailDTO.getActivityId(),
                    JacksonUtil.writeValueAsString(detailDTO),
                    ACTIVITY_TIMEOUT);
        }catch (Exception e){
            log.error("redis缓存异常,cacheActivity ActivityDetailDTO:{}",
                    JacksonUtil.writeValueAsString(detailDTO),e);

        }
    }

    private ActivityDetailDTO convertToActivityDetailDTO(ActivityDO activityDO, List<ActivityUserDO> activityUserDOList,
                                                         List<PrizeDO> prizeDOList, List<ActivityPrizeDO> activityPrizeDOList)
    {
        ActivityDetailDTO detailDTO=new ActivityDetailDTO();
        detailDTO.setActivityId(activityDO.getId());
        detailDTO.setActivityName(activityDO.getActivityName());
        detailDTO.setDesc(activityDO.getDescription());
        //这里此时的活动状态时RUNNING
        detailDTO.setStatus(ActivityStatusEnum.forName(activityDO.getStatus()));

        List<ActivityDetailDTO.PrizeDTO> prizeDTOList=activityPrizeDOList
                .stream()
//        从代码分析来看，红色圈圈的 activityPrizeDO 指的是 activityPrizeDOList 中的每个元素对象
                .map(activityPrizeDO -> {
                    ActivityDetailDTO.PrizeDTO prizeDTO=new ActivityDetailDTO.PrizeDTO();
                    //要确保活动关联奖品表的id与奖品表的id同
                    prizeDTO.setPrizeId(activityPrizeDO.getPrizeId());
                    //这个相当于就是取出所有的prizeDO  条件是prizeDO.getId().equals(activityPrizeDO.getPrizeId()
                    Optional<PrizeDO> optionalPrizeDO=prizeDOList.stream()
                            .filter(prizeDO -> prizeDO.getId().equals(activityPrizeDO.getPrizeId()))
                            //可能出现有同一个id的情况，返回相同id的第一个就行
                            .findFirst();
                    //如果PrizeDO为空，不执行当前方法，不为空才执行  即就是找到了符合条件的PrizeDO才执行这个方法
                    optionalPrizeDO.ifPresent(prizeDO -> {
                        prizeDTO.setName(prizeDO.getName());
                        prizeDTO.setImageUrl(prizeDO.getImageUrl());
                        prizeDTO.setPrice(prizeDO.getPrice());
                        prizeDTO.setDescription(prizeDO.getDescription());
                    });
                    //这个等待前端的传递
                    prizeDTO.setTiers(ActivityPrizeTiersEnum.forName(activityPrizeDO.getPrizeTiers()));
                    prizeDTO.setPrizeAmount(activityPrizeDO.getPrizeAmount());
                     //这个是init
                    prizeDTO.setStatus(ActivityPrizeStatusEnum.forName(activityPrizeDO.getStatus()));
                    return prizeDTO;
                }).collect(Collectors.toList());
        detailDTO.setPrizeDTOList(prizeDTOList);

        List<ActivityDetailDTO.UserDTO> userDTOList=activityUserDOList.stream()
                //audo就是activityUserDOList中的对象 就是活动人员表
                .map(auDo->{
                    ActivityDetailDTO.UserDTO userDTO=new ActivityDetailDTO.UserDTO();
                    userDTO.setUserId(auDo.getUserId());
                    userDTO.setUserName(auDo.getUserName());
                    userDTO.setStatus(ActivityUserStatusEnum.forName(auDo.getStatus()));
                    return userDTO;
                }).collect(Collectors.toList());
        detailDTO.setUserDTOList(userDTOList);
        return detailDTO;
    }

    @Override
    public void cacheActivity(Long activityId) {
        if(activityId==null){
            log.warn("要查询的活动id为空!");
            throw new ServiceException(ServiceErrorCodeConstants.CACHE_ACTIVITY_ID_IS_EMPTY);
        }
        //查询表数据：活动表 活动奖品表 活动人员表 奖品表
        ActivityDO aDO=activityMapper.selectById(activityId);
        if(aDO==null){
            log.error("要缓存的活动id有误!");
            throw new ServiceException(ServiceErrorCodeConstants.CACHE_ACTIVITY_ID_ERROR);
        }
        //活动奖品表
        List<ActivityPrizeDO> apDOList=activityPrizeMapper.selectByActivityId(activityId);
        //活动人员表
        List<ActivityUserDO> auDOList=activityUserMapper.selectByActivityID(activityId);
        //奖品表
        List<Long> prizeIds=apDOList.stream()
                .map(ActivityPrizeDO::getPrizeId)
                .collect(Collectors.toList());
        List<PrizeDO> pDOList=prizeMapper.batchSelectByIds(prizeIds);
        //整合详细 信息 放入redis
        cacheActivity(convertToActivityDetailDTO(aDO,auDOList,pDOList,apDOList));
    }

    /**
     * 检验活动是否有效
     * @param param
     */
    private void checkActivity(CreateActivityParam param) {
        if(param==null){
            throw new ServiceException(ServiceErrorCodeConstants.CREATE_ACTIVITY_INFO_IS_EMPTY);
        }
        // 检查参加活动的人员id是否在user表里面存在
        //使用流式进行数据的提取或映射
        List<Long> userIds = param.getActivityUserList()
                .stream()
                .map(CreateUserByActivityParam::getUserId)
                .distinct()   //去重id 有可能传递了多个重复的id
                .collect(Collectors.toList());
        List<Long> existUserIds=userMapper.selectExistByIds(userIds);
        //这个是经过调试 有可能出现existUserIds为null的情况 则传递的id 数据库中根本就没有的
        //TODO 把这个 代码注释 ，调试看效果
        if(CollectionUtils.isEmpty(existUserIds)){
            throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_USER_ERROR);
        }
        //这个是应对 有可能你传递的id是（1，2，3） 但是你数据库的id是（1，2，5） 有一个id不一致
        userIds.forEach(id ->{
            if(!existUserIds.contains(id)){
                throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_USER_ERROR);
            }
        });
        // 检查参加活动的奖品id是否在prize表里面存在
        //使用流式进行数据的提取或映射
        List<Long> prizeIds = param.getActivityPrizeList()
                .stream()
                .map(CreatePrizeByActivityParam::getPrizeId)
                .distinct()   //去重id 有可能传递了多个重复的id
                .collect(Collectors.toList());
        List<Long> existPrizeIds=prizeMapper.selectExistByIds(prizeIds);
        //TODO 把这个 代码注释 ，调试看效果
        if(CollectionUtils.isEmpty(existPrizeIds)){
            throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_PRIZE_ERROR);
        }
        prizeIds.forEach(id ->{
            if(!existPrizeIds.contains(id)){
                throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_PRIZE_ERROR);
            }
        });
        // 人员数量大于等于奖品数量
        //这个可以得到有几个用户参与抽奖活动
        int uesrAmount=param.getActivityUserList().size();
        //这个可以得到有多少种奖品
        long prizeAmount=param.getActivityPrizeList()
                .stream()
                .mapToLong(CreatePrizeByActivityParam::getPrizeAmount)
                .sum();
        //不允许有奖品剩余，但允许uesrAmount>prizeAmount
        if(uesrAmount<prizeAmount){
            throw new ServiceException(ServiceErrorCodeConstants.USER_PRIZE_AMOUNT_ERROR);
        }
        //校验活动奖品等级的有效性
        param.getActivityPrizeList().forEach(prize->{
            if(ActivityPrizeTiersEnum.forName(prize.getPrizeTiers())==null){
                throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_PRIZE_TIERS_ERROR);
            }
        });
    }

    @Override
    public PageListDTO<ActivityDTO> findActivityList(PageParam param) {
        //获取总量
        int total=activityMapper.count();

        //获取当前页的列表
        List<ActivityDO> activityDOList=activityMapper.selectActivityList(param.offset(),param.getPageSize());
        List<ActivityDTO> activityDTOList=activityDOList.stream()
                .map(activityDO -> {
                    ActivityDTO activityDTO=new ActivityDTO();
                    activityDTO.setActivityId(activityDO.getId());
                    activityDTO.setActivityName(activityDO.getActivityName());
                    activityDTO.setDescription(activityDO.getDescription());
                    activityDTO.setStatus(ActivityStatusEnum.forName(activityDO.getStatus()));
                    return activityDTO;
                }).collect(Collectors.toList());
        return new PageListDTO<>(total,activityDTOList);
    }

    @Override
    public ActivityDetailDTO getActivityDetail(Long activityId) {
        if(activityId==null){
            log.warn("查询活动详细信息失败,activityId为空!");
            return null;
        }
        //查询redis
        ActivityDetailDTO detailDTO=getActivityFromCache(activityId);
        if(detailDTO!=null){
            log.info("查询活动详细信息成功! detailDTO={}",
                    JacksonUtil.writeValueAsString(detailDTO));
            return detailDTO;
        }
        //如果redis不存在，则查表
        //活动表
        ActivityDO aDO=activityMapper.selectById(activityId);
        List<ActivityPrizeDO> apDOList=activityPrizeMapper.selectByActivityId(activityId);
        List<ActivityUserDO> auDOList=activityUserMapper.selectByActivityID(activityId);
        List<Long> prizeIds=apDOList.stream()
                .map(ActivityPrizeDO::getPrizeId)
                .collect(Collectors.toList());
        List<PrizeDO> pDOList=prizeMapper.batchSelectByIds(prizeIds);
        detailDTO=convertToActivityDetailDTO(aDO,auDOList,pDOList,apDOList);
        //将活动相信信息放入redis中
        cacheActivity(detailDTO);
        return detailDTO;
    }

    private ActivityDetailDTO getActivityFromCache(Long activityId) {
        if(activityId==null){
            log.warn("获取缓存活动数据的activityId为空!");
            return null;
        }
        try{
            String str=redisUtil.get(ACTIVITY_PREFIX+activityId);
            if(!StringUtils.hasText(str)){
                log.info("获取的缓存活动数据为空! key={}",ACTIVITY_PREFIX+activityId);
                return null;
            }
            //反序列化
            return JacksonUtil.readValue(str,ActivityDetailDTO.class);
        }catch (Exception e){
            log.error("从缓存中获取活动信息异常,key={}",ACTIVITY_PREFIX+activityId,e);
            return null;
        }
    }
}
